/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.core;

import cn.seaboot.commons.convert.math.BigDecimalToNumberConverterFactory;
import cn.seaboot.commons.convert.math.StringToNumberConverterFactory;
import cn.seaboot.commons.convert.sql.*;
import cn.seaboot.commons.convert.time.*;
import cn.seaboot.commons.lang.Warning;
import org.jetbrains.annotations.NotNull;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

/**
 * 数据格式转换工具
 * <p>
 * 1、pattern转换逻辑过于混乱，为了避免代码变得复杂，删除了全部带pattern的函数，
 * 2、数字/日期只保留默认的格式转换功能，一个配置全局生效。
 * 3、底层实现使用spring的ConversionService，可以与spring-context的共享ConversionService
 *
 * <p> 2017/08/22 01:07:17 压制任何警告的数据格式转换
 * <p> 2019/08/27 17:15:17 日期、数字格式化优化
 * <p> 2020/01/08 21:34:17 规范语法，遇到无法强转的数据类型，抛出ClassCastException
 * <p> 2020/07/13 21:34:17 期类型代码调优，toBigDecimal()函数，不再默认保留小数
 * <p> 2020/11/03 21:34:17 增加对java.time，3个日期的支持，并且代码回滚，保留java.sql的期格式
 * <p> 2020/11/13 21:34:17 删除全部带pattern的函数，如需做特殊格式转换，请使用专门的工具
 * <p> 2020/11/23 21:42:17 只用1个工具类，代码过于臃肿，不利于未来发展，与Spring的共享ConversionService。
 * <p> 2023/10/30 11:40:44 高版本 spring 底层增加了过多逻辑，考虑使用低版本 spring 的 api
 *
 * @author Mr.css 2017/8/22下午1:07:17
 */
public class Converter {
    private Converter() {
    }

    //GenericConversionService start----------------------------------------------------------------------------

    /**
     * GenericConversionService
     */
    private static GenericConversionService conversionService;

    static {
        conversionService = new DefaultConversionService();
        useSql(conversionService);
        useJava8(conversionService);
        useScientificNotation(conversionService);
    }

    /**
     * 使用java.sql的日期类型
     *
     * @param conversionService GenericConversionService
     */
    public static void useSql(GenericConversionService conversionService) {
        // if use java.sql api
        conversionService.addConverter(Long.class, java.sql.Timestamp.class, new LongToTimestampConverter());
        conversionService.addConverter(java.sql.Timestamp.class, Long.class, new TimestampToLongConverter());

        //字符串转日期
        conversionService.addConverter(String.class, java.sql.Date.class, new StringToDateConverter());
        conversionService.addConverter(String.class, java.util.Date.class, new StringToUtilDateConverter());
        conversionService.addConverter(String.class, java.sql.Time.class, new StringToTimeConverter());
        conversionService.addConverter(String.class, java.sql.Timestamp.class, new StringToTimestampConverter());

        //日期转字符串
        conversionService.addConverter(java.sql.Date.class, String.class, new SqlDateToStringConverter());
        conversionService.addConverter(java.sql.Time.class, String.class, new TimeToStringConverter());
        conversionService.addConverter(java.util.Date.class, String.class, new UtilDateToStringConverter());

        //为了兼容一些旧的代码
        conversionService.addConverter(java.util.Date.class, LocalDate.class, new DateToLocalDateConverter());
        conversionService.addConverter(java.util.Date.class, LocalDateTime.class, new DateToLocalDateTimeConverter());
        conversionService.addConverter(java.util.Date.class, LocalTime.class, new DateToLocalTimeConverter());
    }


    /**
     * 使用科学记数法作为数字的转换
     *
     * @param conversionService GenericConversionService
     */
    public static void useScientificNotation(GenericConversionService conversionService) {
        if (conversionService.canConvert(String.class, Number.class)) {
            conversionService.removeConvertible(String.class, Number.class);
        }
        conversionService.addConverterFactory(new StringToNumberConverterFactory());
        conversionService.addConverterFactory(new BigDecimalToNumberConverterFactory());
    }

    /**
     * 使用java8的日期
     *
     * @param conversionService GenericConversionService
     */
    public static void useJava8(GenericConversionService conversionService) {
        // 日期转字符串
        conversionService.addConverter(java.time.LocalDate.class, String.class, new LocalDateToStringConverter());
        conversionService.addConverter(java.time.LocalTime.class, String.class, new LocalTimeToStringConverter());
        conversionService.addConverter(java.time.LocalDateTime.class, String.class, new LocalDateTimeToStringConverter());
        conversionService.addConverter(java.time.LocalDateTime.class, Long.class, new LocalDateTimeToLongConverter());

        //字符串转日期
        conversionService.addConverter(String.class, java.time.LocalDate.class, new StringToLocalDateConverter());
        conversionService.addConverter(String.class, java.time.LocalTime.class, new StringToLocalTimeConverter());
        conversionService.addConverter(String.class, java.time.LocalDateTime.class, new StringToLocalDateTimeConverter());
        conversionService.addConverter(Long.class, java.time.LocalDateTime.class, new LongToLocalDateTimeConverter());
    }

    //GenericConversionService end----------------------------------------------------------------------------

    /**
     * 数据转换为目标格式
     *
     * @param obj value
     * @param def default value
     * @param <T> Object
     * @return target type Object
     */
    public static <T> T convert(Object obj, Class<T> clazz, @NotNull T def) {
        T t = convert(obj, clazz);
        return t == null ? def : t;
    }

    /**
     * 数据转换为目标格式。
     *
     * 业务中会出于需要，会简写一些全类名，这个函数主要用于兼容这种设计。
     * 因为用到了Class.forName()函数，这个步骤较为耗时，不推荐用于转换非常规的类。
     *
     * @param obj  value
     * @param type target type
     * @return target type Object
     */
    @SuppressWarnings(Warning.UNCHECKED)
    public static <T> T convert(Object obj, String type) {
        if (obj == null) {
            return null;
        } else if (type.length() <= 10) {
            switch (type) {
                case "String":
                    return (T) toString(obj);
                case "Boolean":
                    return (T) toBool(obj);
                case "boolean":
                    return (T) toBool(obj, false);
                case "Integer":
                    return (T) toInteger(obj);
                case "int":
                    return (T) toInteger(obj, 0);
                case "Long":
                    return (T) toLong(obj);
                case "long":
                    return (T) toLong(obj, 0L);
                case "Float":
                    return (T) toFloat(obj);
                case "float":
                    return (T) toFloat(obj, 0f);
                case "Double":
                    return (T) toDouble(obj);
                case "double":
                    return (T) toDouble(obj, 0d);
                case "BigDecimal":
                    return (T) toBigDecimal(obj);
                default:
                    break;
            }
        } else if (obj.getClass().getName().equals(type)) {
            return (T) obj;
        }
        try {
            return (T) convert(obj, Class.forName(type));
        } catch (ClassNotFoundException e) {
            throw new ClassCastException(obj + " can not case to: " + type);
        }
    }

    /**
     * 数据转换为目标格式
     *
     * 挺想把下面代码还原成低版本的代码，现在 spring 的设计过重了
     *
     * @param obj   value
     * @param clazz target type
     * @param <T>   Object
     * @return target type Object
     */
    public static <T> T convert(Object obj, Class<T> clazz) {
        return conversionService.convert(obj, clazz);
    }

    /**
     * Object -> String
     *
     * @param obj Object
     * @param def default value
     * @return String
     */
    public static @NotNull String toString(Object obj, @NotNull String def) {
        return nvl(toString(obj), def);
    }

    /**
     * Object -> String
     *
     * @param obj Object
     * @return String
     */
    public static String toString(Object obj) {
        return conversionService.convert(obj, String.class);
    }

    /**
     * Object -> BigDecimal
     *
     * @param obj Object
     * @param def default value
     * @return BigDecimal
     */
    public static @NotNull BigDecimal toBigDecimal(Object obj, @NotNull BigDecimal def) {
        return nvl(toBigDecimal(obj), def);
    }

    /**
     * Object -> BigDecimal
     *
     * @param obj Object
     * @return BigDecimal
     */
    public static BigDecimal toBigDecimal(Object obj) {
        return conversionService.convert(obj, BigDecimal.class);
    }

    /**
     * Object -> Double
     *
     * @param obj Object
     * @param def default value
     * @return Double
     */
    public static @NotNull Double toDouble(Object obj, @NotNull Double def) {
        return nvl(toDouble(obj), def);
    }

    /**
     * Object -> Double
     *
     * @param obj Object
     * @return Double
     */
    public static Double toDouble(Object obj) {
        return conversionService.convert(obj, Double.class);
    }

    /**
     * Object -> Long
     *
     * @param obj Object
     * @param def default value
     * @return Long
     */
    public static @NotNull Long toLong(Object obj, @NotNull Long def) {
        return nvl(toLong(obj), def);
    }

    /**
     * Object -> Long
     *
     * @param obj Object
     * @return Long
     */
    public static Long toLong(Object obj) {
        return conversionService.convert(obj, Long.class);
    }

    /**
     * Object -> Float
     *
     * @param obj Object
     * @return Float
     */
    public static @NotNull Float toFloat(Object obj, @NotNull Float def) {
        return nvl(toFloat(obj), def);
    }

    /**
     * Object -> Float
     *
     * @param obj Object
     * @return Float
     */
    public static Float toFloat(Object obj) {
        return conversionService.convert(obj, Float.class);
    }

    /**
     * Object -> Integer
     *
     * @param obj Object
     * @param def default value
     * @return Integer
     */
    public static @NotNull Integer toInteger(Object obj, @NotNull Integer def) {
        return nvl(toInteger(obj), def);
    }

    /**
     * Object -> Integer
     *
     * @param obj Object
     * @return Integer
     */
    public static Integer toInteger(Object obj) {
        return conversionService.convert(obj, Integer.class);
    }

    /**
     * Object -> Boolean
     *
     * @param obj Object
     * @return Boolean
     */
    public static @NotNull Boolean toBool(Object obj, @NotNull Boolean def) {
        return nvl(toBool(obj), def);
    }

    /**
     * Object -> Boolean
     *
     * @param obj Object
     * @return Boolean
     */
    public static Boolean toBool(Object obj) {
        return conversionService.convert(obj, Boolean.class);
    }

    /**
     * Object -> java.sql.Date
     *
     * @param obj Object
     * @param def default value
     * @return java.sql.Date
     */
    public static @NotNull java.sql.Date toDate(Object obj, @NotNull java.util.Date def) {
        return nvl(toDate(obj), new java.sql.Date(def.getTime()));
    }

    /**
     * Object -> String
     *
     * @param obj Object
     * @param def default value
     * @return String
     */
    public static @NotNull java.sql.Date toDate(Object obj, @NotNull java.sql.Date def) {
        return nvl(toDate(obj), def);
    }

    /**
     * Object -> java.sql.Date
     *
     * @param obj Object
     * @return String
     */
    public static java.sql.Date toDate(Object obj) {
        return conversionService.convert(obj, java.sql.Date.class);
    }

    /**
     * Object -> java.util.Date
     *
     * @param obj Object
     * @param def default value
     * @return java.util.Date
     */
    public static @NotNull java.util.Date toUtilDate(Object obj, @NotNull java.util.Date def) {
        return nvl(toUtilDate(obj), def);
    }

    /**
     * Object -> java.util.Date
     *
     * @param obj Object
     * @return String
     */
    public static java.util.Date toUtilDate(Object obj) {
        return conversionService.convert(obj, java.util.Date.class);
    }

    /**
     * Object -> java.sql.Time
     *
     * @param obj Object
     * @param def default value
     * @return java.sql.Time
     */
    public static @NotNull java.sql.Time toTime(Object obj, @NotNull java.util.Date def) {
        return nvl(toTime(obj), new java.sql.Time(def.getTime()));
    }

    /**
     * Object -> java.sql.Time
     *
     * @param obj Object
     * @return java.sql.Time
     */
    public static java.sql.Time toTime(Object obj) {
        return conversionService.convert(obj, java.sql.Time.class);
    }

    /**
     * Object -> java.sql.Timestamp
     *
     * @param obj Object
     * @param def default value
     * @return java.sql.Timestamp
     */
    public static @NotNull java.sql.Timestamp toTimestamp(Object obj, @NotNull java.util.Date def) {
        return nvl(toTimestamp(obj), new java.sql.Timestamp(def.getTime()));
    }

    /**
     * Object -> java.sql.Timestamp
     *
     * @param obj Object
     * @return java.sql.Timestamp
     */
    public static java.sql.Timestamp toTimestamp(Object obj) {
        return conversionService.convert(obj, java.sql.Timestamp.class);
    }

    /**
     * Object -> LocalTime
     *
     * @param obj Object
     * @param def default value
     * @return LocalTime
     */
    public static @NotNull LocalTime toLocalTime(Object obj, @NotNull LocalTime def) {
        return nvl(toLocalTime(obj), def);
    }

    /**
     * Object -> LocalTime
     *
     * @param obj Object
     * @return LocalTime
     */
    public static LocalTime toLocalTime(Object obj) {
        return conversionService.convert(obj, LocalTime.class);
    }


    /**
     * Object -> LocalDate
     *
     * @param obj Object
     * @param def default value
     * @return LocalDate
     */
    public static @NotNull LocalDate toLocalDate(Object obj, @NotNull LocalDate def) {
        return nvl(toLocalDate(obj), def);
    }

    /**
     * Object -> java.sql.Time
     *
     * @param obj Object
     * @return LocalTime
     */
    public static LocalDate toLocalDate(Object obj) {
        return conversionService.convert(obj, LocalDate.class);
    }


    /**
     * Object -> LocalDateTime
     *
     * @param obj Object
     * @param def default value
     * @return LocalDateTime
     */
    public static @NotNull LocalDateTime toLocalDateTime(Object obj, @NotNull LocalDateTime def) {
        return nvl(toLocalDateTime(obj), def);
    }

    /**
     * Object -> LocalDateTime
     *
     * @param obj Object
     * @return LocalDateTime
     */
    public static LocalDateTime toLocalDateTime(Object obj) {
        return conversionService.convert(obj, LocalDateTime.class);
    }

    /**
     * check null, if null, return default value
     *
     * @param src    value
     * @param newObj default value
     * @return default value
     */
    private static <T> T nvl(T src, T newObj) {
        return src == null ? newObj : src;
    }

    //getter/setter-------------------------------------------------------------------------------

    /**
     * return {@link DefaultConversionService}
     *
     * @return GenericConversionService
     */
    public static ConversionService getConversionService() {
        return conversionService;
    }

    /**
     * had better get GenericConversionService form spring-context
     *
     * @param conversionService GenericConversionService
     */
    public static void setConversionService(GenericConversionService conversionService) {
        Converter.conversionService = conversionService;
    }
}
